import { join } from 'path';
import * as fs from 'fs-extra';
import axios from 'axios';

const currentDirPath = join(__dirname, '../../', 'res/download');

// 获取文件大小字符串
function getFileSizeStr(length) {
  let size = Number(length);
  let unit = 'B';
  if (size > 1000) {
    size /= 1024;
    unit = 'KB';
  }
  if (size > 1000) {
    size /= 1024;
    unit = 'MB';
  }
  if (size > 1000) {
    size /= 1024;
    unit = 'GB';
  }
  if (size > 1000) {
    size /= 1024;
    unit = 'TB';
  }
  return size.toFixed(2) + unit;
}

function formatHeaders(headers) {
  return Object.keys(headers).reduce((header, name) => {
    header[String(name).toLowerCase()] = headers[name];
    return header;
  }, {});
}

// 获取请求头
async function getResHeader(url, defaultHeaders, retryCount) {
  let headers = Object.assign(
    {
      'Cache-Control': 'no-cache',
      Connection: 'keep-alive',
      Pragma: 'no-cache',
      Range: 'bytes=0-1',
    },
    defaultHeaders,
  );

  try {
    let response = await axios({
      timeout: 60000,
      method: 'get',
      url,
      headers,
    });
    return formatHeaders(response.headers);
  } catch (e) {
    retryCount -= 1;
    if (retryCount > 0) {
      return getResHeader(url, defaultHeaders, retryCount);
    } else {
      throw e;
    }
  }
}

// 获取不重复的名字
function getName(downloadDir, name) {
  name = decodeURIComponent(name);
  let nameArr = name.split('.');
  let ext = nameArr.pop();
  let oldName = nameArr.join('.');
  let fileName = oldName;
  let index = 0;
  while (true) {
    try {
      fs.accessSync(join(downloadDir, fileName + '.' + ext), fs.constants.F_OK);
      index++;
      fileName = `${oldName} (${index})`;
    } catch (err) {
      return [fileName, ext].join('.');
    }
  }
}

// 下载
const download = async (
  url,
  tempPath,
  defaultHeaders,
  retryCount,
  start = 0,
  end,
) => {
  let headers = Object.assign(
    {
      'Cache-Control': 'no-cache',
      Connection: 'keep-alive',
      Pragma: 'no-cache',
    },
    defaultHeaders,
  );

  try {
    let response = await axios({
      timeout: 60 * 1000,
      method: 'get',
      responseType: 'stream',
      url,
      headers,
    });
    let responseHeaders = formatHeaders(response.headers);
    let fileLength = Number(responseHeaders['content-length']);

    return new Promise((resolve, reject) => {
      let readerStream = response.data.pipe(
        fs.createWriteStream(tempPath, {
          start: 0,
          flags: 'r+',
          autoClose: true,
        }),
      );
      // 等待5分钟还没下载完成则中断下载
      let timer;
      let setTimer = () => {
        if (timer) {
          clearTimeout(timer);
        }
        timer = setTimeout(() => {
          readerStream.end();
          reject(new Error('download fail'));
        }, 10 * 60 * 1000);
      };
      setTimer();

      readerStream.on('finish', () => {
        clearTimeout(timer);
        timer = null;

        if (fileLength === readerStream.bytesWritten) {
          resolve();
        } else {
          resolve(
            download(url, tempPath, defaultHeaders, retryCount, start, end),
          );
        }
      });
      readerStream.on('error', err => {
        clearTimeout(timer);
        timer = null;
        reject(err);
      });
      readerStream.on('drain', () => {
        setTimer();
      });
    });
  } catch (e) {
    retryCount -= 1;
    if (retryCount > 0) {
      return download(url, tempPath, defaultHeaders, retryCount, start, end);
    } else {
      throw e;
    }
  }
};

// 分段下载
async function multiThreadDownload(
  fileBuffer,
  url,
  fileName,
  filePath,
  defaultHeaders,
  headers,
  retryCount,
) {
  // 生成临时文件目录
  let downloadList = fileBuffer.map(({ start, end }) => {
    let tempPath = join(filePath, '.download_cache/' + fileName + '/');
    let tempFilePath = join(tempPath, start + '-' + end + '.tmp');

    return {
      start,
      end,
      tempPath,
      tempFilePath,
    };
  });

  // 已下载的模块数量
  let downloadedSize = 0;

  await taskSlice(
    downloadList,
    32,
    async ({ start, end, tempFilePath, tempPath }) => {
      // 创建临时文件
      fs.ensureDirSync(tempPath);
      // 判断临时文件是否存在
      if (fs.existsSync(tempFilePath)) {
        let fileLength = await new Promise((resolve, reject) => {
          fs.readFile(tempFilePath, (err, data) => {
            if (err) {
              reject(err);
            }
            resolve(data.length);
          });
        });
        if (fileLength >= end - start) {
          downloadedSize++;
          return Promise.resolve();
        }
      }
      fs.appendFileSync(tempFilePath, new Uint8Array(0));

      try {
        let header = Object.assign({}, defaultHeaders, {
          etag: headers['etag'],
          'Content-Type': headers['content-type'],
          Range: 'bytes=' + start + '-' + end,
        });
        await download(url, tempFilePath, header, retryCount, start, end);
        downloadedSize++;
      } catch (e) {
        fs.removeSync(tempFilePath);
        throw e;
      }
    },
  );

  let writeStream = fs.createWriteStream(filePath);

  for (let i = 0; i < downloadList.length; i++) {
    let tempFilePath = downloadList[i].tempFilePath;
    await new Promise((resolve, reject) => {
      let readerStream = fs.createReadStream(tempFilePath);
      readerStream.pipe(writeStream, { end: false });

      readerStream.on('end', () => {
        resolve();
      });
      readerStream.on('error', err => {
        reject(err);
      });
    });
  }
  writeStream.end('down');
  fs.removeSync(downloadList[0].tempPath);
}

// 文件分片
function splitBlock(blockSize, fileLength) {
  let blockList = [];
  let block = 0;
  while (block < fileLength) {
    let end = block + blockSize - 1;
    if (end > fileLength) {
      end = fileLength;
    }
    blockList.push({ start: block, end: end });
    block += blockSize;
  }
  return blockList;
}

// 将列表任务分成多分执行，callback 必须返回 Promise 对象
const taskSlice = async (list, count, callback) => {
  if (!list.length) {
    return null;
  }
  let handlerStack = [];
  let errorStack = [];
  let tempList = list.concat();
  let result;

  return new Promise((resolve, reject) => {
    let handler = async item => {
      if (item) {
        try {
          await callback(item);
        } catch (e) {
          errorStack.push(e);
          console.log((e && e.stack) || e);
        }

        let next = tempList.shift();
        if (next) {
          handlerStack.push(handler(next));
        } else {
          if (!result) {
            result = Promise.all(handlerStack).then(() => {
              return Promise.reject(new Error('Some tasks failed to execute'));
            });
          }
          resolve(result);
        }
      }
    };
    for (let i = 0; i < count; i++) {
      handlerStack.push(handler(tempList.shift()));
    }
  });
};

// 批量下载
const sendRequest = async (
  url,
  downloadDir = currentDirPath,
  defaultHeaders,
  retryCount,
) => {
  let headers = await getResHeader(url, defaultHeaders, retryCount);
  let fileName = getName(
    downloadDir,
    url
      .split('?')[0]
      .split('/')
      .pop(),
  );
  let filePath = join(downloadDir, fileName);
  let fileBuffer = null;
  // 分块大小 4M
  let blockSize = 1024 * 1024 * 4;
  let fileLength = 0;

  if (headers && headers['content-range']) {
    fileLength = Number(headers['content-range'].split('/').pop());

    if (fileLength > blockSize) {
      fileBuffer = splitBlock(blockSize, fileLength);
    }
  }

  if (!Array.isArray(fileBuffer) || fileBuffer?.length) {
    fileBuffer = [{ start: 0, end: fileLength }];
  }

  try {
    // 分段下载
    await multiThreadDownload(
      fileBuffer,
      url,
      fileName,
      filePath,
      defaultHeaders,
      headers,
      retryCount,
    );
  } catch (e) {
    throw e;
  }
};

const saveFile = (data, name, dir) => {
  fs.writeFile(join(dir, name), data, err => {
    if (err) throw err;
  });
};

export { sendRequest };
